/*
 * SoapUI, Copyright (C) 2004-2017 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.impl.rest.support;

import com.eviware.soapui.impl.rest.RestMethod;
import com.eviware.soapui.impl.rest.RestRepresentation;
import com.eviware.soapui.impl.rest.RestRequestInterface;
import com.eviware.soapui.impl.rest.RestResource;
import com.eviware.soapui.impl.rest.RestService;
import com.eviware.soapui.impl.rest.support.RestParamsPropertyHolder.ParameterStyle;
import com.eviware.soapui.impl.support.definition.support.InvalidDefinitionException;
import com.eviware.soapui.impl.wsdl.support.Constants;
import com.eviware.soapui.impl.wsdl.support.UrlSchemaLoader;
import com.eviware.soapui.impl.wsdl.support.xsd.SchemaUtils;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.Tools;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.xml.XmlUtils;
import com.eviware.soapui.tools.PropertyExpansionRemover;
import net.java.dev.wadl.x2009.x02.ApplicationDocument;
import net.java.dev.wadl.x2009.x02.ApplicationDocument.Application;
import net.java.dev.wadl.x2009.x02.DocDocument.Doc;
import net.java.dev.wadl.x2009.x02.MethodDocument.Method;
import net.java.dev.wadl.x2009.x02.ParamDocument.Param;
import net.java.dev.wadl.x2009.x02.ParamStyle;
import net.java.dev.wadl.x2009.x02.RepresentationDocument;
import net.java.dev.wadl.x2009.x02.RepresentationDocument.Representation;
import net.java.dev.wadl.x2009.x02.ResourceDocument.Resource;
import net.java.dev.wadl.x2009.x02.ResourceTypeDocument;
import net.java.dev.wadl.x2009.x02.ResourcesDocument.Resources;
import net.java.dev.wadl.x2009.x02.ResponseDocument.Response;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.namespace.QName;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class WadlImporter {
    private RestService service;
    private Application application;
    private List<Resources> resourcesList;
    private Map<String, ApplicationDocument> refCache = new HashMap<String, ApplicationDocument>();
    private boolean isWADL11 = true;

    public WadlImporter(RestService service) {
        this.service = service;
    }

    public void initFromWadl(String wadlUrl) {
        try {
            // XmlObject xmlObject = XmlObject.Factory.parse( new URL( wadlUrl ) );
            XmlObject xmlObject = XmlUtils.createXmlObject(new URL(wadlUrl));

            String content = xmlObject.xmlText();
            Element element = ((Document) xmlObject.getDomNode()).getDocumentElement();

            // try to allow older namespaces
            if (element.getLocalName().equals("application")
                    && element.getNamespaceURI().startsWith("http://research.sun.com/wadl")) {
                isWADL11 = false;
                content = content.replaceAll("\"" + element.getNamespaceURI() + "\"", "\"" + Constants.WADL11_NS + "\"");
            } else if (!element.getLocalName().equals("application")
                    || !element.getNamespaceURI().equals(Constants.WADL11_NS)) {
                throw new Exception("Document is not a WADL application with " + Constants.WADL11_NS + " namespace");
            }
            content = PropertyExpansionRemover.removeExpansions(content);
            ApplicationDocument applicationDocument = ApplicationDocument.Factory.parse(content);
            application = applicationDocument.getApplication();

            resourcesList = application.getResourcesList();

            service.setName(getFirstTitle(application.getDocList(), service.getName()));

            String base = resourcesList.size() == 1 ? resourcesList.get(0).getBase() : "";

            try {
                URL baseUrl = new URL(base);
                service.setBasePath(baseUrl.getPath());

                service.addEndpoint(Tools.getEndpointFromUrl(baseUrl));
            } catch (Exception e) {
                service.setBasePath(base);
            }

            service.setWadlUrl(wadlUrl);
            service.getConfig().setWadlVersion(isWADL11 ? Constants.WADL11_NS : Constants.WADL10_NS);

            for (Resources resources : resourcesList) {
                RestResource baseResource = null;
                if (resourcesList.size() > 1) {
                    String path = resources.getBase();
                    baseResource = service.addNewResource(path, path);
                }
                for (Resource resource : resources.getResourceList()) {
                    String name = getFirstTitle(resource.getDocList(), resource.getPath());
                    String path = resource.getPath();

                    RestResource newResource = null;

                    if (baseResource != null && path != null) {
                        for (RestResource res : baseResource.getChildResourceList()) {
                            if (path.equals(res.getPath())) {
                                newResource = res;
                                break;
                            }
                        }

                        if (newResource == null) {
                            newResource = baseResource.addNewChildResource(name, path);
                        }
                    } else if (path != null) {
                        for (RestResource res : service.getResourceList()) {
                            if (path.equals(res.getPath())) {
                                newResource = res;
                                break;
                            }
                        }

                        if (newResource == null) {
                            newResource = service.addNewResource(name, path);
                        }
                    } else {
                        newResource = service.addNewResource(name, "");
                    }

                    initResourceFromWadlResource(newResource, resource);
                    addSubResources(newResource, resource);
                }
            }
        } catch (InvalidDefinitionException ex) {
            ex.show();
        } catch (Exception e) {
            UISupport.showErrorMessage(e);
        }
    }

    private void addSubResources(RestResource newResource, Resource resource) {
        for (Resource res : resource.getResourceList()) {
            String path = res.getPath();
            if (path == null) {
                path = "";
            }

            String name = getFirstTitle(res.getDocList(), path);

            RestResource newRes = null;

            for (RestResource child : newResource.getChildResourceList()) {
                if (path.equals(child.getPath())) {
                    newRes = child;
                    break;
                }
            }

            if (newRes == null) {
                newRes = newResource.addNewChildResource(name, path);
            }

            initResourceFromWadlResource(newRes, res);

            addSubResources(newRes, res);
        }
    }

    private String getFirstTitle(List<Doc> list, String defaultTitle) {
        for (Doc doc : list) {
            if (StringUtils.hasContent(doc.getTitle())) {
                return doc.getTitle();
            }
        }
        return defaultTitle;
    }

    private void initResourceFromWadlResource(RestResource newResource, Resource resource) {
        for (Param param : resource.getParamList()) {
            param = resolveParameter(param);
            if (param != null) {
                String nm = param.getName();
                RestParamProperty prop = newResource.hasProperty(nm) ? newResource.getProperty(nm) : newResource
                        .addProperty(nm);

                initParam(param, prop);
            }
        }

        for (Method method : resource.getMethodList()) {
            method = resolveMethod(method);
            initMethod(newResource, method);
        }

        List<?> types = resource.getType();
        if (types != null && types.size() > 0) {
            for (Object obj : types) {
                ResourceTypeDocument.ResourceType type = resolveResource(obj.toString());
                if (type != null) {
                    for (Method method : type.getMethodList()) {
                        method = resolveMethod(method);
                        RestMethod restMethod = initMethod(newResource, method);

                        for (Param param : type.getParamList()) {
                            param = resolveParameter(param);
                            if (param != null) {
                                String nm = param.getName();
                                RestParamProperty prop = restMethod.hasProperty(nm) ? restMethod.getProperty(nm)
                                        : restMethod.addProperty(nm);

                                initParam(param, prop);
                            }
                        }
                    }
                }
            }
        }
    }

    private RestMethod initMethod(RestResource newResource, Method method) {
        // build name
        String name = getFirstTitle(method.getDocList(), method.getName());
        String id = method.getId();
        if (StringUtils.hasContent(id) && !id.trim().equals(name.trim())) {
            name += " - " + method.getId();
        }

        // ensure unique name
        if (newResource.getRestMethodByName(name) != null) {
            int cnt = 0;
            String orgName = name;

            while (newResource.getRestMethodByName(name) != null) {
                cnt++;
                name = orgName + "-" + cnt;
            }
        }

        // add to resource
        RestMethod restMethod = newResource.addNewMethod(name);
        restMethod.setMethod(RestRequestInterface.HttpMethod.valueOf(method.getName()));

        if (method.getRequest() != null) {
            for (Param param : method.getRequest().getParamList()) {
                param = resolveParameter(param);
                if (param != null) {
                    RestParamProperty p = restMethod.addProperty(param.getName());
                    initParam(param, p);
                }
            }

            for (Representation representation : method.getRequest().getRepresentationList()) {
                representation = resolveRepresentation(representation);
                addRepresentationFromConfig(restMethod, representation, RestRepresentation.Type.REQUEST, null);
            }
        }

        for (Response response : method.getResponseList()) {
            for (Representation representation : response.getRepresentationList()) {
                addRepresentation(response, restMethod, representation);
            }
            if (!isWADL11) {
                NodeList children = response.getDomNode().getChildNodes();
                for (int i = 0; i < children.getLength(); i++) {
                    Node n = children.item(i);
                    if ("fault".equals(n.getNodeName())) {
                        String content = XmlUtils.serialize(n, false);
                        try {
                            Map<Object, Object> map = new HashMap<Object, Object>();
                            XmlCursor cursor = response.newCursor();
                            cursor.getAllNamespaces(map);
                            cursor.dispose();
                            XmlOptions options = new XmlOptions();
                            options.setLoadAdditionalNamespaces(map);
                            // XmlObject obj = XmlObject.Factory.parse(
                            // content.replaceFirst( "<(([a-z]+:)?)fault ",
                            // "<$1representation " ), options );
                            XmlObject obj = XmlUtils.createXmlObject(
                                    content.replaceFirst("<(([a-z]+:)?)fault ", "<$1representation "), options);
                            RepresentationDocument representation = (RepresentationDocument) obj
                                    .changeType(RepresentationDocument.type);
                            addRepresentation(response, restMethod, representation.getRepresentation());
                        } catch (XmlException e) {
                        }
                    }
                }
            }
        }

        restMethod.addNewRequest("Request 1");
        return restMethod;
    }

    private void addRepresentation(Response response, RestMethod restMethod, Representation representation) {
        representation = resolveRepresentation(representation);
        List<Long> status = null;
        if (isWADL11) {
            status = response.getStatus();
        } else {
            Node n = representation.getDomNode().getAttributes().getNamedItem("status");
            if (n != null) {
                status = new ArrayList<Long>();
                for (String s : n.getNodeValue().split(" ")) {
                    status.add(Long.parseLong(s));
                }
            }
        }
        boolean fault = false;
        if (status != null && status.size() > 0) {
            fault = true;
            for (Long s : status) {
                if (s < 400) {
                    fault = false;
                    break;
                }
            }
        }
        RestRepresentation.Type type = fault ? RestRepresentation.Type.FAULT : RestRepresentation.Type.RESPONSE;
        addRepresentationFromConfig(restMethod, representation, type, status);
    }

    private void addRepresentationFromConfig(RestMethod restMethod, Representation representation,
                                             RestRepresentation.Type type, List<?> status) {
        RestRepresentation restRepresentation = restMethod.addNewRepresentation(type);
        restRepresentation.setMediaType(representation.getMediaType());
        restRepresentation.setElement(representation.getElement());
        if (status != null) {
            restRepresentation.setStatus(status);
        }
        restRepresentation.setId(representation.getId());
        restRepresentation.setDescription(getFirstTitle(representation.getDocList(), null));
    }

    private void initParam(Param param, RestParamProperty prop) {
        prop.setDefaultValue(param.getDefault());
        prop.setValue(param.getDefault());
        ParamStyle.Enum paramStyle = param.getStyle();
        if (paramStyle == null) {
            paramStyle = ParamStyle.QUERY;
        }

        prop.setStyle(ParameterStyle.valueOf(paramStyle.toString().toUpperCase()));
        prop.setRequired(param.getRequired());
        QName paramType = param.getType();
        prop.setType(paramType);

        String[] options = new String[param.sizeOfOptionArray()];
        for (int c = 0; c < options.length; c++) {
            options[c] = param.getOptionArray(c).getValue();
        }

        if (options.length > 0) {
            prop.setOptions(options);
        }
    }

    private Method resolveMethod(Method method) {
        String href = method.getHref();
        if (!StringUtils.hasContent(href)) {
            return method;
        }

        for (Method m : application.getMethodList()) {
            if (m.getId().equals(href.substring(1))) {
                return m;
            }
        }

        try {
            ApplicationDocument applicationDocument = loadReferencedWadl(href);
            if (applicationDocument != null) {
                int ix = href.lastIndexOf('#');
                if (ix > 0) {
                    href = href.substring(ix + 1);
                }

                for (Method m : application.getMethodList()) {
                    if (m.getId().equals(href)) {
                        return m;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return method;
    }

    private Representation resolveRepresentation(Representation representation) {
        String href = representation.getHref();
        if (!StringUtils.hasContent(href)) {
            return representation;
        }

        try {
            Application app = application;

            if (!href.startsWith("#")) {
                ApplicationDocument applicationDocument = loadReferencedWadl(href);
                app = applicationDocument.getApplication();
            }

            if (app != null) {
                int ix = href.lastIndexOf('#');
                if (ix >= 0) {
                    href = href.substring(ix + 1);
                }

                for (Representation m : application.getRepresentationList()) {
                    if (m.getId().equals(href)) {
                        return m;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return representation;
    }

    private Param resolveParameter(Param param) {
        String href = param.getHref();
        if (!StringUtils.hasContent(href)) {
            return param;
        }

        try {
            Application app = application;

            if (!href.startsWith("#")) {
                ApplicationDocument applicationDocument = loadReferencedWadl(href);
                app = applicationDocument.getApplication();
            }

            if (app != null) {
                int ix = href.lastIndexOf('#');
                if (ix >= 0) {
                    href = href.substring(ix + 1);
                }

                for (Param p : application.getParamList()) {
                    if (p.getId().equals(href)) {
                        return p;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

	/*
     * private Representation resolveFault( Representation representation ) {
	 * String href = representation.getHref(); if( !StringUtils.hasContent( href
	 * ) ) return representation;
	 * 
	 * try { ApplicationDocument applicationDocument = loadReferencedWadl( href
	 * ); if( applicationDocument != null ) { int ix = href.lastIndexOf( '#' );
	 * if( ix > 0 ) href = href.substring( ix + 1 );
	 * 
	 * for( Representation m : application.getFaultList() ) { if(
	 * m.getId().equals( href ) ) return m; } } } catch( Exception e ) {
	 * e.printStackTrace(); }
	 * 
	 * return representation; }
	 */

    private ResourceTypeDocument.ResourceType resolveResource(String id) {
        for (ResourceTypeDocument.ResourceType resourceType : application.getResourceTypeList()) {
            if (resourceType.getId().equals(id)) {
                return resourceType;
            }
        }

        try {
            ApplicationDocument applicationDocument = loadReferencedWadl(id);
            if (applicationDocument != null) {
                int ix = id.lastIndexOf('#');
                if (ix > 0) {
                    id = id.substring(ix + 1);
                }

                for (ResourceTypeDocument.ResourceType resourceType : applicationDocument.getApplication()
                        .getResourceTypeList()) {
                    if (resourceType.getId().equals(id)) {
                        return resourceType;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    private ApplicationDocument loadReferencedWadl(String id) throws URISyntaxException, XmlException, IOException {
        int ix = id.indexOf('#');
        if (ix != -1) {
            id = id.substring(0, ix);
        }
        ApplicationDocument applicationDocument = refCache.get(id);

        if (applicationDocument == null) {
            URI uri = new URI(id);
            applicationDocument = ApplicationDocument.Factory.parse(uri.toURL());
            refCache.put(id, applicationDocument);
        }

        return applicationDocument;
    }

    public static Map<String, XmlObject> getDefinitionParts(String wadlUrl) {
        Map<String, XmlObject> result = new HashMap<String, XmlObject>();

        try {
            return SchemaUtils.getSchemas(wadlUrl, new UrlSchemaLoader(wadlUrl));

            // URL url = new URL(wadlUrl);
            // ApplicationDocument applicationDocument =
            // ApplicationDocument.Factory.parse(url);
            // result.put(url.getPath(), applicationDocument);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return result;
    }

    public static String extractParams(URL param, RestParamsPropertyHolder params) {
        String path = param.getPath();
        String[] items = path.split("/");

        int templateParamCount = 0;
        StringBuffer resultPath = new StringBuffer();

        for (int i = 0; i < items.length; i++) {
            String item = items[i];
            try {
                String[] matrixParams = item.split(";");
                if (matrixParams.length > 0) {
                    item = matrixParams[0];
                    for (int c = 1; c < matrixParams.length; c++) {
                        String matrixParam = matrixParams[c];

                        int ix = matrixParam.indexOf('=');
                        if (ix == -1) {
                            params.addProperty(URLDecoder.decode(matrixParam, "Utf-8")).setStyle(ParameterStyle.MATRIX);
                        } else {
                            String name = matrixParam.substring(0, ix);
                            RestParamProperty property = params.addProperty(URLDecoder.decode(name, "Utf-8"));
                            property.setStyle(ParameterStyle.MATRIX);
                            property.setValue(URLDecoder.decode(matrixParam.substring(ix + 1), "Utf-8"));
                        }
                    }
                }

                Integer.parseInt(item);
                RestParamProperty prop = params.addProperty("param" + templateParamCount++);
                prop.setStyle(ParameterStyle.TEMPLATE);
                prop.setValue(item);

                item = "{" + prop.getName() + "}";
            } catch (Exception e) {
            }

            if (StringUtils.hasContent(item)) {
                resultPath.append('/').append(item);
            }
        }

        String query = ((URL) param).getQuery();
        if (StringUtils.hasContent(query)) {
            items = query.split("&");
            for (String item : items) {
                try {
                    int ix = item.indexOf('=');
                    if (ix == -1) {
                        params.addProperty(URLDecoder.decode(item, "Utf-8")).setStyle(ParameterStyle.QUERY);
                    } else {
                        String name = item.substring(0, ix);
                        RestParamProperty property = params.addProperty(URLDecoder.decode(name, "Utf-8"));
                        property.setStyle(ParameterStyle.QUERY);
                        property.setValue(URLDecoder.decode(item.substring(ix + 1), "Utf-8"));
                    }
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }
        }

        return resultPath.toString();
    }
}
